package besta.moon.sql.expression;

import java.sql.*;

import besta.moon.MoonException;
import besta.moon.sql.Expression;
import besta.moon.sql.datatype.DateTime;
import besta.moon.sql.datatype.Money;
import besta.moon.sql.datatype.MutableNumeric;
import besta.moon.sql.expression.function.string.FunctionRTrim;
import besta.moon.sql.expression.function.system.FunctionConvert;
import besta.moon.sql.parser.SQLTokenizer;
import besta.moon.util.MoonUtils;
import besta.moon.MoonCode;

public class ExpressionArithmetic extends Expression {

    private Expression left;
    private Expression right;
    private Expression right2;
    private Expression[] inList;
    final private int operation;

    public ExpressionArithmetic(Expression left, int operation) {
        super(FUNCTION);
        this.left = left;
        this.operation = operation;
        super.setParams(new Expression[]{left});
    }

    public ExpressionArithmetic(Expression left, Expression right, int operation) {
        super(FUNCTION);
        this.left = left;
        this.right = right;
        this.operation = operation;
        super.setParams(new Expression[]{left, right});
    }

    public ExpressionArithmetic(Expression left, Expression right, Expression right2, int operation) {
        super(FUNCTION);
        this.left = left;
        this.right = right;
        this.right2 = right2;
        this.operation = operation;
        super.setParams(new Expression[]{left, right, right2});
    }

    public ExpressionArithmetic(Expression left, Expressions inList, int operation) {
        super(FUNCTION);
        this.left = left;
        this.operation = operation;
        Expression[] params;
        if (inList != null) {
            this.inList = inList.toArray();
            params = new Expression[this.inList.length + 1];
            params[0] = left;
            System.arraycopy(this.inList, 0, params, 1, this.inList.length);
        } else {
            params = new Expression[]{left};
        }
        super.setParams(params);
    }

    public int getOperation() {
        return operation;
    }

    private Expression convertExpressionIfNeeded(Expression expr, Expression other) {
        if (expr == null || other == null) {
            return expr;
        }
        switch (expr.getDataType()) {
            case SQLTokenizer.CHAR, SQLTokenizer.NCHAR, SQLTokenizer.BINARY -> {
                switch (other.getDataType()) {
                    case SQLTokenizer.VARCHAR, SQLTokenizer.NVARCHAR, SQLTokenizer.CLOB, SQLTokenizer.NCLOB, SQLTokenizer.LONGNVARCHAR, SQLTokenizer.LONGVARCHAR, SQLTokenizer.VARBINARY -> {
                        FunctionRTrim trim = new FunctionRTrim();
                        trim.setParams(new Expression[]{expr});
                        return trim;
                }
                    case SQLTokenizer.CHAR, SQLTokenizer.NCHAR, SQLTokenizer.BINARY -> {
                        if (other.getPrecision() > expr.getPrecision()) {
                            return new FunctionConvert(new ColumnExpression(other), expr, null);
                        }
                }
                }
            }
        }
        return expr;
    }

    @Override
    public final void setParamAt(Expression param, int idx) {
        switch (idx) {
            case 0 -> left = param;
            case 1 -> {
                if (right != null) {
                    right = param;
                }
            }
            case 2 -> {
                if (right != null) {
                    right2 = param;
                }
            }
        }
        if (inList != null && idx > 0 && idx <= inList.length) {
            inList[idx - 1] = param;
        }
        super.setParamAt(param, idx);
    }

    @Override
    public boolean equals(Object expr) {
        if (!super.equals(expr)) {
            return false;
        }
        if (!(expr instanceof ExpressionArithmetic)) {
            return false;
        }
        return ((ExpressionArithmetic) expr).operation == operation;
    }

    @Override
    public int getInt() throws java.lang.Exception {
        if (isNull()) {
            return 0;
        }
        int dataType = getDataType();
        switch (dataType) {
            case SQLTokenizer.BIT, SQLTokenizer.BOOLEAN -> {
                return getBoolean() ? 1 : 0;
            }
            case SQLTokenizer.TINYINT, SQLTokenizer.SMALLINT, SQLTokenizer.INT -> {
                return getIntImpl();
            }
            case SQLTokenizer.BIGINT -> {
                return (int) getLongImpl();
            }
            case SQLTokenizer.REAL -> {
                return (int) getFloatImpl();
            }
            case SQLTokenizer.FLOAT, SQLTokenizer.DOUBLE, SQLTokenizer.MONEY, SQLTokenizer.SMALLMONEY, SQLTokenizer.NUMERIC, SQLTokenizer.DECIMAL -> {
                return (int) getDoubleImpl();
            }
        }
        throw createUnspportedConversion(SQLTokenizer.INT);
    }

    private int getIntImpl() throws java.lang.Exception {
        switch (operation) {
            case ADD -> {
                return left.getInt() + right.getInt();
            }
            case SUB -> {
                return left.getInt() - right.getInt();
            }
            case MUL -> {
                return left.getInt() * right.getInt();
            }
            case DIV -> {
                return left.getInt() / right.getInt();
            }
            case NEGATIVE -> {
                return -left.getInt();
            }
            case MOD -> {
                return left.getInt() % right.getInt();
            }
            case BIT_NOT -> {
                return ~left.getInt();
            }
        }
        throw createUnspportedConversion(SQLTokenizer.INT);
    }

    @Override
    public long getLong() throws java.lang.Exception {
        if (isNull()) {
            return 0;
        }
        int dataType = getDataType();
        switch (dataType) {
            case SQLTokenizer.BIT, SQLTokenizer.BOOLEAN -> {
                return getBoolean() ? 1 : 0;
            }
            case SQLTokenizer.TINYINT, SQLTokenizer.SMALLINT, SQLTokenizer.INT -> {
                return getIntImpl();
            }
            case SQLTokenizer.BIGINT -> {
                return getLongImpl();
            }
            case SQLTokenizer.REAL -> {
                return (long) getFloatImpl();
            }
            case SQLTokenizer.FLOAT, SQLTokenizer.DOUBLE, SQLTokenizer.MONEY, SQLTokenizer.SMALLMONEY, SQLTokenizer.NUMERIC, SQLTokenizer.DECIMAL -> {
                return (long) getDoubleImpl();
            }
        }
        throw createUnspportedConversion(SQLTokenizer.LONG);
    }

    private long getLongImpl() throws java.lang.Exception {
        if (isNull()) {
            return 0;
        }
        switch (operation) {
            case ADD -> {
                return left.getLong() + right.getLong();
            }
            case SUB -> {
                return left.getLong() - right.getLong();
            }
            case MUL -> {
                return left.getLong() * right.getLong();
            }
            case DIV -> {
                return left.getLong() / right.getLong();
            }
            case NEGATIVE -> {
                return -left.getLong();
            }
            case MOD -> {
                return left.getLong() % right.getLong();
            }
            case BIT_NOT -> {
                return ~right.getInt();
            }
        }
        throw createUnspportedConversion(SQLTokenizer.LONG);
    }

    @Override
    public double getDouble() throws java.lang.Exception {
        if (isNull()) {
            return 0;
        }
        int dataType = getDataType();
        switch (dataType) {
            case SQLTokenizer.BIT, SQLTokenizer.BOOLEAN -> {
                return getBoolean() ? 1 : 0;
            }
            case SQLTokenizer.TINYINT, SQLTokenizer.SMALLINT, SQLTokenizer.INT -> {
                return getIntImpl();
            }
            case SQLTokenizer.BIGINT -> {
                return getLongImpl();
            }
            case SQLTokenizer.REAL -> {
                return getFloatImpl();
            }
            case SQLTokenizer.FLOAT, SQLTokenizer.DOUBLE, SQLTokenizer.MONEY, SQLTokenizer.SMALLMONEY, SQLTokenizer.NUMERIC, SQLTokenizer.DECIMAL -> {
                return getDoubleImpl();
            }
        }
        throw createUnspportedConversion(SQLTokenizer.DOUBLE);
    }

    private double getDoubleImpl() throws java.lang.Exception {
        if (operation == NEGATIVE) {
            return getDoubleImpl(0, left.getDouble());
        }
        return getDoubleImpl(left.getDouble(), right.getDouble());
    }

    private double getDoubleImpl(double lVal, double rVal) throws java.lang.Exception {
        switch (operation) {
            case ADD -> {
                return lVal + rVal;
            }
            case SUB -> {
                return lVal - rVal;
            }
            case MUL -> {
                return lVal * rVal;
            }
            case DIV -> {
                return lVal / rVal;
            }
            case NEGATIVE -> {
                return -rVal;
            }
            case MOD -> {
                return lVal % rVal;
            }
        }
        throw createUnspportedConversion(SQLTokenizer.DOUBLE);
    }

    @Override
    public float getFloat() throws java.lang.Exception {
        if (isNull()) {
            return 0;
        }
        int dataType = getDataType();
        switch (dataType) {
            case SQLTokenizer.BIT, SQLTokenizer.BOOLEAN -> {
                return getBoolean() ? 1 : 0;
            }
            case SQLTokenizer.TINYINT, SQLTokenizer.SMALLINT, SQLTokenizer.INT -> {
                return getIntImpl();
            }
            case SQLTokenizer.BIGINT -> {
                return getLongImpl();
            }
            case SQLTokenizer.REAL -> {
                return getFloatImpl();
            }
            case SQLTokenizer.FLOAT, SQLTokenizer.DOUBLE, SQLTokenizer.MONEY, SQLTokenizer.SMALLMONEY, SQLTokenizer.NUMERIC, SQLTokenizer.DECIMAL -> {
                return (float) getDoubleImpl();
            }
        }
        throw createUnspportedConversion(SQLTokenizer.DOUBLE);
    }

    private float getFloatImpl() throws java.lang.Exception {
        switch (operation) {
            case ADD -> {
                return left.getFloat() + right.getFloat();
            }
            case SUB -> {
                return left.getFloat() - right.getFloat();
            }
            case MUL -> {
                return left.getFloat() * right.getFloat();
            }
            case DIV -> {
                return left.getFloat() / right.getFloat();
            }
            case NEGATIVE -> {
                return -left.getFloat();
            }
            case MOD -> {
                return left.getFloat() % right.getFloat();
            }
        }
        throw createUnspportedConversion(SQLTokenizer.REAL);
    }

    @Override
    public long getMoney() throws java.lang.Exception {
        if (isNull()) {
            return 0;
        }
        int dataType = getDataType();
        switch (dataType) {
            case SQLTokenizer.BIT, SQLTokenizer.BOOLEAN -> {
                return getBoolean() ? 10000 : 0;
            }
            case SQLTokenizer.TINYINT, SQLTokenizer.SMALLINT, SQLTokenizer.INT -> {
                return getIntImpl() * 10000;
            }
            case SQLTokenizer.BIGINT -> {
                return getLongImpl() * 10000;
            }
            case SQLTokenizer.REAL -> {
                return MoonUtils.doubleToMoney(getFloatImpl());
            }
            case SQLTokenizer.FLOAT, SQLTokenizer.DOUBLE, SQLTokenizer.NUMERIC, SQLTokenizer.DECIMAL -> {
                return MoonUtils.doubleToMoney(getDoubleImpl());
            }
            case SQLTokenizer.MONEY, SQLTokenizer.SMALLMONEY -> {
                return getMoneyImpl();
            }
        }
        throw createUnspportedConversion(SQLTokenizer.DOUBLE);
    }

    private long getMoneyImpl() throws java.lang.Exception {
        switch (operation) {
            case ADD -> {
                return left.getMoney() + right.getMoney();
            }
            case SUB -> {
                return left.getMoney() - right.getMoney();
            }
            case MUL -> {
                return left.getMoney() * right.getMoney() / 10000;
            }
            case DIV -> {
                return left.getMoney() * 10000 / right.getMoney();
            }
            case NEGATIVE -> {
                return -left.getMoney();
            }
        }
        throw createUnspportedConversion(SQLTokenizer.MONEY);
    }

    @Override
    public MutableNumeric getNumeric() throws java.lang.Exception {
        if (isNull()) {
            return null;
        }
        int dataType = getDataType();
        switch (dataType) {
            case SQLTokenizer.BIT, SQLTokenizer.BOOLEAN -> {
                return new MutableNumeric(getBoolean() ? 1 : 0);
            }
            case SQLTokenizer.TINYINT, SQLTokenizer.SMALLINT, SQLTokenizer.INT -> {
                return new MutableNumeric(getIntImpl());
            }
            case SQLTokenizer.BIGINT -> {
                return new MutableNumeric(getLongImpl());
            }
            case SQLTokenizer.REAL -> {
                return new MutableNumeric(getFloatImpl());
            }
            case SQLTokenizer.FLOAT, SQLTokenizer.DOUBLE -> {
                return new MutableNumeric(getDoubleImpl());
            }
            case SQLTokenizer.NUMERIC, SQLTokenizer.DECIMAL -> {
                return getNumericImpl();
            }
            case SQLTokenizer.MONEY, SQLTokenizer.SMALLMONEY -> {
                return new MutableNumeric(getMoneyImpl(), 4);
            }
        }
        throw createUnspportedConversion(SQLTokenizer.DOUBLE);
    }

    private MutableNumeric getNumericImpl() throws java.lang.Exception {
        switch (operation) {
            case ADD -> {
                MutableNumeric num = left.getNumeric();
                num.add(right.getNumeric());
                return num;
            }
            case SUB -> {
                MutableNumeric num = left.getNumeric();
                num.sub(right.getNumeric());
                return num;
            }
            case MUL -> {
                if (getDataType(right.getDataType(), SQLTokenizer.INT) == SQLTokenizer.INT) {
                    MutableNumeric num = left.getNumeric();
                    num.mul(right.getInt());
                    return num;
                } else if (getDataType(left.getDataType(), SQLTokenizer.INT) == SQLTokenizer.INT) {
                    MutableNumeric num = right.getNumeric();
                    num.mul(left.getInt());
                    return num;
                } else {
                    MutableNumeric num = left.getNumeric();
                    num.mul(right.getNumeric());
                    return num;
                }
            }
            case DIV -> {
                MutableNumeric num = left.getNumeric();
                if (getDataType(right.getDataType(), SQLTokenizer.INT) == SQLTokenizer.INT) {
                    num.div(right.getInt());
                } else {
                    num.div(right.getNumeric());
                }
                return num;
            }
            case NEGATIVE -> {
                MutableNumeric num = left.getNumeric();
                num.setSignum(-num.getSignum());
                return num;
            }
            case MOD -> {
                if (getDataType(getDataType(), SQLTokenizer.INT) == SQLTokenizer.INT) {
                    return new MutableNumeric(getInt());
                }
                MutableNumeric num = left.getNumeric();
                num.mod(right.getNumeric());
                return num;
            }
            default -> throw createUnspportedConversion(SQLTokenizer.NUMERIC);
        }
    }

    @Override
    public Object getObject() throws java.lang.Exception {
        if (isNull()) {
            return null;
        }
        int dataType = getDataType();
        switch (dataType) {
            case SQLTokenizer.BIT, SQLTokenizer.BOOLEAN -> {
                return getBoolean() ? Boolean.TRUE : Boolean.FALSE;
            }
            case SQLTokenizer.BINARY, SQLTokenizer.VARBINARY -> {
                return getBytes();
            }
            case SQLTokenizer.TINYINT, SQLTokenizer.SMALLINT, SQLTokenizer.INT -> {
                return getInt();
            }
            case SQLTokenizer.BIGINT -> {
                return getLong();
            }
            case SQLTokenizer.REAL -> {
                return getFloat();
            }
            case SQLTokenizer.FLOAT, SQLTokenizer.DOUBLE -> {
                return getDouble();
            }
            case SQLTokenizer.MONEY, SQLTokenizer.SMALLMONEY -> {
                return Money.createFromUnscaledValue(getMoney());
            }
            case SQLTokenizer.NUMERIC, SQLTokenizer.DECIMAL -> {
                return getNumeric();
            }
            case SQLTokenizer.CHAR, SQLTokenizer.NCHAR, SQLTokenizer.VARCHAR, SQLTokenizer.NVARCHAR, SQLTokenizer.LONGNVARCHAR, SQLTokenizer.LONGVARCHAR -> {
                return getString(left.getString(), right.getString());
            }
            case SQLTokenizer.JAVA_OBJECT -> {
                Object lObj = left.getObject();
                Object rObj = right.getObject();
                if (lObj instanceof Number && rObj instanceof Number) {
                    return getDoubleImpl(((Number) lObj).doubleValue(), ((Number) rObj).doubleValue());
                } else {
                    return getString(lObj.toString(), rObj.toString());
                }
            }
            case SQLTokenizer.LONGVARBINARY -> {
                return getBytes();
            }
            case SQLTokenizer.DATE, SQLTokenizer.TIME, SQLTokenizer.TIMESTAMP, SQLTokenizer.SMALLDATETIME -> {
                return new DateTime(getLong(), dataType);
            }
            case SQLTokenizer.UNIQUEIDENTIFIER -> {
                return getBytes();
            }
            default -> throw createUnspportedDataType();
        }
    }

    @Override
    public boolean getBoolean() throws java.lang.Exception {
        switch (operation) {
            case OR -> {
                return left.getBoolean() || right.getBoolean();
            }
            case AND -> {
                return left.getBoolean() && right.getBoolean();
            }
            case NOT -> {
                return !left.getBoolean();
            }
            case LIKE -> {
                return MoonUtils.like(left.getString(), right.getString());
            }
            case ISNULL -> {
                return left.isNull();
            }
            case ISNOTNULL -> {
                return !left.isNull();
            }
            case IN -> {
                if (right == null) {
                    return isInList();
                }
            }
        }
        final boolean leftIsNull = left.isNull();
        int dataType;
        if (operation == NEGATIVE || operation == BIT_NOT) {
            if (leftIsNull) {
                return false;
            }
            dataType = left.getDataType();
        } else {
            final boolean rightIsNull = right.isNull();
            if (operation == EQUALS_NULL && leftIsNull && rightIsNull) {
                return true;
            }
            if (leftIsNull || rightIsNull) {
                return false;
            }
            dataType = getDataType(left, right);
        }
        switch (dataType) {
            case SQLTokenizer.BOOLEAN:
                switch (operation) {
                    case IN, EQUALS_NULL, EQUALS -> {
                        return left.getBoolean() == right.getBoolean();
                }
                    case UNEQUALS -> {
                        return left.getBoolean() != right.getBoolean();
                }
                }

            case SQLTokenizer.TINYINT:
            case SQLTokenizer.SMALLINT:
            case SQLTokenizer.INT:
            case SQLTokenizer.BIT:
                switch (operation) {
                    case IN, EQUALS_NULL, EQUALS -> {
                        return left.getInt() == right.getInt();
                }
                    case GREATER -> {
                        return left.getInt() > right.getInt();
                }
                    case GRE_EQU -> {
                        return left.getInt() >= right.getInt();
                }
                    case LESSER -> {
                        return left.getInt() < right.getInt();
                }
                    case LES_EQU -> {
                        return left.getInt() <= right.getInt();
                }
                    case UNEQUALS -> {
                        return left.getInt() != right.getInt();
                }
                    case BETWEEN -> {
                        int _left = left.getInt();
                        return _left >= right.getInt() && right2.getInt() >= _left;
                }
                    default -> {
                        return getInt() != 0;
                }
                }

            case SQLTokenizer.BIGINT:
            case SQLTokenizer.TIMESTAMP:
            case SQLTokenizer.TIME:
            case SQLTokenizer.DATE:
            case SQLTokenizer.SMALLDATETIME:
                switch (operation) {
                    case IN, EQUALS_NULL, EQUALS -> {
                        return left.getLong() == right.getLong();
                }
                    case GREATER -> {
                        return left.getLong() > right.getLong();
                }
                    case GRE_EQU -> {
                        return left.getLong() >= right.getLong();
                }
                    case LESSER -> {
                        return left.getLong() < right.getLong();
                }
                    case LES_EQU -> {
                        return left.getLong() <= right.getLong();
                }
                    case UNEQUALS -> {
                        return left.getLong() != right.getLong();
                }
                    case BETWEEN -> {
                        long _left = left.getLong();
                        return _left >= right.getLong() && right2.getLong() >= _left;
                }
                    default -> {
                        return getLong() != 0;
                }
                }

            case SQLTokenizer.REAL:
                switch (operation) {
                    case IN, EQUALS_NULL, EQUALS -> {
                        return left.getFloat() == right.getFloat();
                }
                    case GREATER -> {
                        return left.getFloat() > right.getFloat();
                }
                    case GRE_EQU -> {
                        return left.getFloat() >= right.getFloat();
                }
                    case LESSER -> {
                        return left.getFloat() < right.getFloat();
                }
                    case LES_EQU -> {
                        return left.getFloat() <= right.getFloat();
                }
                    case UNEQUALS -> {
                        return left.getFloat() != right.getFloat();
                }
                    case BETWEEN -> {
                        float _left = left.getFloat();
                        return _left >= right.getFloat() && right2.getFloat() >= _left;
                }
                    default -> {
                        return getFloat() != 0;
                }
                }

            case SQLTokenizer.FLOAT:
            case SQLTokenizer.DOUBLE:
                switch (operation) {
                    case IN, EQUALS_NULL, EQUALS -> {
                        return left.getDouble() == right.getDouble();
                }
                    case GREATER -> {
                        return left.getDouble() > right.getDouble();
                }
                    case GRE_EQU -> {
                        return left.getDouble() >= right.getDouble();
                }
                    case LESSER -> {
                        return left.getDouble() < right.getDouble();
                }
                    case LES_EQU -> {
                        return left.getDouble() <= right.getDouble();
                }
                    case UNEQUALS -> {
                        return left.getDouble() != right.getDouble();
                }
                    case BETWEEN -> {
                        double _left = left.getDouble();
                        return _left >= right.getDouble() && right2.getDouble() >= _left;
                }
                    default -> {
                        return getDouble() != 0;
                }
                }

            case SQLTokenizer.MONEY:
            case SQLTokenizer.SMALLMONEY:
                switch (operation) {
                    case IN, EQUALS_NULL, EQUALS -> {
                        return left.getMoney() == right.getMoney();
                }
                    case GREATER -> {
                        return left.getMoney() > right.getMoney();
                }
                    case GRE_EQU -> {
                        return left.getMoney() >= right.getMoney();
                }
                    case LESSER -> {
                        return left.getMoney() < right.getMoney();
                }
                    case LES_EQU -> {
                        return left.getMoney() <= right.getMoney();
                }
                    case UNEQUALS -> {
                        return left.getMoney() != right.getMoney();
                }
                    case BETWEEN -> {
                        long _left = left.getMoney();
                        return _left >= right.getMoney() && right2.getMoney() >= _left;
                }
                    default -> {
                        return getMoney() != 0;
                }
                }

            case SQLTokenizer.DECIMAL:
            case SQLTokenizer.NUMERIC: {
                if (operation == NEGATIVE) {
                    return left.getNumeric().getSignum() != 0;
                }
                int comp = left.getNumeric().compareTo(right.getNumeric());
            return switch (operation) {
                case IN, EQUALS_NULL, EQUALS -> comp == 0;
                case GREATER -> comp > 0;
                case GRE_EQU -> comp >= 0;
                case LESSER -> comp < 0;
                case LES_EQU -> comp <= 0;
                case UNEQUALS -> comp != 0;
                case BETWEEN -> comp >= 0 && 0 >= left.getNumeric().compareTo(right2.getNumeric());
                default -> getNumeric().getSignum() != 0;
            };
            }
            case SQLTokenizer.VARCHAR:
            case SQLTokenizer.NVARCHAR:
            case SQLTokenizer.CHAR:
            case SQLTokenizer.NCHAR:
            case SQLTokenizer.LONGVARCHAR:
            case SQLTokenizer.LONGNVARCHAR:
            case SQLTokenizer.CLOB: {
                final String leftStr = left.getString();
                final String rightStr = right.getString();
                int comp = String.CASE_INSENSITIVE_ORDER.compare(leftStr, rightStr);
                switch (operation) {
                    case IN, EQUALS_NULL, EQUALS -> {
                        return comp == 0;
                }
                    case GREATER -> {
                        return comp > 0;
                }
                    case GRE_EQU -> {
                        return comp >= 0;
                }
                    case LESSER -> {
                        return comp < 0;
                }
                    case LES_EQU -> {
                        return comp <= 0;
                }
                    case UNEQUALS -> {
                        return comp != 0;
                }
                    case BETWEEN -> {
                        return comp >= 0 && 0 >= String.CASE_INSENSITIVE_ORDER.compare(leftStr, right2.getString());
                }
                    case ADD -> {
                        return MoonUtils.string2boolean(leftStr + rightStr);
                }
                }
                break;
            }
            case SQLTokenizer.BINARY:
            case SQLTokenizer.VARBINARY:
            case SQLTokenizer.LONGVARBINARY:
            case SQLTokenizer.BLOB:
            case SQLTokenizer.UNIQUEIDENTIFIER: {
                byte[] leftBytes = left.getBytes();
                byte[] rightBytes = right.getBytes();
                int comp = MoonUtils.compareBytes(leftBytes, rightBytes);
                switch (operation) {
                    case IN, EQUALS_NULL, EQUALS -> {
                        return comp == 0;
                }
                    case GREATER -> {
                        return comp > 0;
                }
                    case GRE_EQU -> {
                        return comp >= 0;
                }
                    case LESSER -> {
                        return comp < 0;
                }
                    case LES_EQU -> {
                        return comp <= 0;
                }
                    case UNEQUALS -> {
                        return comp != 0;
                }
                    case BETWEEN -> {
                        return comp >= 0 && 0 >= MoonUtils.compareBytes(leftBytes, right2.getBytes());
                }
                }
                break;
            }
        }
        throw createUnspportedDataType();
    }

    @Override
    public String getString() throws java.lang.Exception {
        if (isNull()) {
            return null;
        }
        return getObject().toString();
    }

    private String getString(String lVal, String rVal) throws java.lang.Exception {
        switch (operation) {
            case ADD -> {
                return lVal + rVal;
            }
        }
        throw createUnspportedConversion(SQLTokenizer.VARCHAR);
    }

    @Override
    public int getDataType() {
        return switch (operation) {
            case NEGATIVE, BIT_NOT -> left.getDataType();
            case EQUALS, EQUALS_NULL, GREATER, GRE_EQU, LESSER, LES_EQU, UNEQUALS, BETWEEN, OR, AND, NOT, LIKE, ISNULL, ISNOTNULL -> SQLTokenizer.BOOLEAN;
            default -> getDataType(left, right);
        };
    }

    @Override
    public int getScale() {
        int dataType = getDataType();
        switch (dataType) {
            case SQLTokenizer.DECIMAL, SQLTokenizer.NUMERIC -> {
                switch (operation) {
                    case ADD, SUB -> {
                        return Math.max(left.getScale(), right.getScale());
                    }
                    case MUL -> {
                        return left.getScale() + right.getScale();
                    }
                    case DIV -> {
                        return Math.max(left.getScale() + 5, right.getScale() + 4);
                    }
                    case NEGATIVE -> {
                        return left.getScale();
                    }
                    case MOD -> {
                        return 0;
                    }
                }
            }

        }
        return getScale(dataType);
    }

    @Override
    public boolean isNull() throws Exception {
        return switch (operation) {
            case OR, AND, NOT, LIKE, ISNULL, ISNOTNULL, IN -> false;
            case NEGATIVE, BIT_NOT -> left.isNull();
            default -> left.isNull() || right.isNull();
        };
    }

    @Override
    public byte[] getBytes() throws java.lang.Exception {
        throw createUnspportedConversion(SQLTokenizer.BINARY);
    }

    boolean isInList() throws Exception {
        if (left.isNull()) {
            return false;
        }
        try {
            for (Expression inList1 : inList) {
                right = inList1;
                if (getBoolean()) {
                    return true;
                }
            }
        } finally {
            right = null;
        }
        return false;
    }

    SQLException createUnspportedDataType() {
        Object[] params = {SQLTokenizer.getKeyWord(getDataType(left, right)), getKeywordFromOperation(operation)};
        return MoonException.create(MoonCode.UNSUPPORTED_DATATYPE_OPER, params);
    }

    SQLException createUnspportedConversion(int dataType) {
        int type = left == null ? right.getDataType() : getDataType(left, right);
        Object[] params = new Object[]{SQLTokenizer.getKeyWord(dataType), SQLTokenizer.getKeyWord(type),
            getKeywordFromOperation(operation)};
        return MoonException.create(MoonCode.UNSUPPORTED_CONVERSION_OPER, params);
    }

    @Override
    public void optimize() throws SQLException {
        super.optimize();
        Expression[] params = getParams();
        if (params.length == 1) {
            return;
        }
        setParamAt(convertExpressionIfNeeded(params[0], params[1]), 0);

        for (int p = 1; p < params.length; p++) {
            setParamAt(convertExpressionIfNeeded(params[p], left), p);
        }
    }

    private static String getKeywordFromOperation(int operation) {
        int token = 0;
        for (int i = 1; i < 1000; i++) {
            if (getOperationFromToken(i) == operation) {
                token = i;
                break;
            }
        }
        if (operation == NEGATIVE) {
            token = SQLTokenizer.MINUS;
        }
        if (operation == ISNOTNULL) {
            token = SQLTokenizer.IS;
        }
        String keyword = SQLTokenizer.getKeyWord(token);
        if (keyword == null) {
            keyword = "" + (char) token;
        }
        return keyword;
    }

    public static int getOperationFromToken(int value) {
        return switch (value) {
            case SQLTokenizer.PLUS -> ADD;
            case SQLTokenizer.MINUS -> SUB;
            case SQLTokenizer.ASTERISK -> MUL;
            case SQLTokenizer.SLACH -> DIV;
            case SQLTokenizer.PERCENT -> MOD;
            case SQLTokenizer.EQUALS -> EQUALS;
            case SQLTokenizer.GREATER -> GREATER;
            case SQLTokenizer.GREATER_EQU -> GRE_EQU;
            case SQLTokenizer.LESSER -> LESSER;
            case SQLTokenizer.LESSER_EQU -> LES_EQU;
            case SQLTokenizer.UNEQUALS -> UNEQUALS;
            case SQLTokenizer.BETWEEN -> BETWEEN;
            case SQLTokenizer.LIKE -> LIKE;
            case SQLTokenizer.IN -> IN;
            case SQLTokenizer.IS -> ISNULL;
            case SQLTokenizer.OR -> OR;
            case SQLTokenizer.AND -> AND;
            case SQLTokenizer.NOT -> NOT;
            case SQLTokenizer.BIT_OR -> BIT_OR;
            case SQLTokenizer.BIT_AND -> BIT_AND;
            case SQLTokenizer.BIT_XOR -> BIT_XOR;
            case SQLTokenizer.TILDE -> BIT_NOT;
            default -> 0;
        };
    }

    public static int getDataType(Expression left, Expression right) {
        int typeLeft = left.getDataType();
        int typeRight = right.getDataType();
        return getDataType(typeLeft, typeRight);
    }

    public static int getBestNumberDataType(int paramDataType) {
        int dataTypeIdx = MoonUtils.indexOf(paramDataType, DatatypeRange);
        if (dataTypeIdx >= NVARCHAR_IDX) {
            return SQLTokenizer.DOUBLE;
        }
        if (dataTypeIdx >= INT_IDX) {
            return SQLTokenizer.INT;
        }
        if (dataTypeIdx >= BIGINT_IDX) {
            return SQLTokenizer.BIGINT;
        }
        if (dataTypeIdx >= MONEY_IDX) {
            return SQLTokenizer.MONEY;
        }
        if (dataTypeIdx >= DECIMAL_IDX) {
            return SQLTokenizer.DECIMAL;
        }
        return SQLTokenizer.DOUBLE;
    }

    public static int getDataType(int typeLeft, int typeRight) {
        if (typeLeft == typeRight) {
            return typeLeft;
        }

        int dataTypeIdx = Math.min(MoonUtils.indexOf(typeLeft, DatatypeRange), MoonUtils.indexOf(typeRight, DatatypeRange));
        if (dataTypeIdx < 0) {
            throw new Error("getDataType(): " + typeLeft + ", " + typeRight);
        }
        return DatatypeRange[dataTypeIdx];
    }

    public static final int OR = 11; // OR
    public static final int AND = 21; // AND
    public static final int NOT = 31; // NOT
    public static final int BIT_OR = 41; // |
    public static final int BIT_AND = 42; // &
    public static final int BIT_XOR = 43; // ^
    public static final int EQUALS = 51; // =
    public static final int EQUALS_NULL = 52; 
    public static final int GREATER = 53; // >
    public static final int GRE_EQU = 54; // >=
    public static final int LESSER = 55; // <
    public static final int LES_EQU = 56; // <=
    public static final int UNEQUALS = 57; // <>
    public static final int IN = 61; // IN
    public static final int BETWEEN = 62; // BETWEEN
    public static final int LIKE = 63; // LIKE
    public static final int ISNULL = 64; // IS NULL
    public static final int ISNOTNULL = ISNULL + 1; // IS NOT NULL
    public static final int ADD = 71; // +
    public static final int SUB = 72; // -
    public static final int MUL = 81; // *
    public static final int DIV = 82; // /
    public static final int MOD = 83; // %
    public static final int BIT_NOT = 91; // ~
    public static final int NEGATIVE = 101; // -

    private static final int[] DatatypeRange = {SQLTokenizer.TIMESTAMP, SQLTokenizer.SMALLDATETIME, SQLTokenizer.DATE,
        SQLTokenizer.TIME, SQLTokenizer.DOUBLE, SQLTokenizer.FLOAT, SQLTokenizer.REAL, SQLTokenizer.DECIMAL,
        SQLTokenizer.NUMERIC, SQLTokenizer.MONEY, SQLTokenizer.SMALLMONEY, SQLTokenizer.BIGINT, SQLTokenizer.INT,
        SQLTokenizer.SMALLINT, SQLTokenizer.TINYINT, SQLTokenizer.BIT, SQLTokenizer.BOOLEAN,
        SQLTokenizer.LONGNVARCHAR, SQLTokenizer.UNIQUEIDENTIFIER, SQLTokenizer.NVARCHAR, SQLTokenizer.NCHAR,
        SQLTokenizer.VARCHAR, SQLTokenizer.CHAR, SQLTokenizer.LONGVARCHAR, SQLTokenizer.CLOB,
        SQLTokenizer.VARBINARY, SQLTokenizer.BINARY, SQLTokenizer.LONGVARBINARY, SQLTokenizer.BLOB,
        SQLTokenizer.NULL};

    private static int NVARCHAR_IDX = MoonUtils.indexOf(SQLTokenizer.NVARCHAR, DatatypeRange);
    private static int INT_IDX = MoonUtils.indexOf(SQLTokenizer.INT, DatatypeRange);
    private static int BIGINT_IDX = MoonUtils.indexOf(SQLTokenizer.BIGINT, DatatypeRange);
    private static int MONEY_IDX = MoonUtils.indexOf(SQLTokenizer.MONEY, DatatypeRange);
    private static int DECIMAL_IDX = MoonUtils.indexOf(SQLTokenizer.DECIMAL, DatatypeRange);
}
